/*
 * sched.c
 * Gestion de l ordonnancement
 *
 */


/*========================================================================
 * Includes
 *========================================================================*/


#include <types.h>
#include <llist.h>
#include "klib.h"
#include "const.h"
#include "thread.h"
#include "sched.h"


/*========================================================================
 * Declaration Private
 *========================================================================*/

PRIVATE struct thread* sched_ready[SCHED_QUEUE_SIZE];
PRIVATE struct thread* sched_running;
PRIVATE struct thread* sched_blocked;
PRIVATE struct thread* sched_dead;

PRIVATE void sched_enqueue_roundrobin(struct thread* th);
PRIVATE void sched_enqueue_staircase(struct thread* th);
PRIVATE u8_t sched_get_higher_prio_queue(void);


/*========================================================================
 * Initialisation
 *========================================================================*/


PUBLIC u8_t sched_init(void)
{
  u8_t i;

  /* Initialisation des listes */
  for(i=0;i<SCHED_QUEUE_SIZE;i++)
    {
      LLIST_NULLIFY(sched_ready[i]);
    }
  LLIST_NULLIFY(sched_running);
  LLIST_NULLIFY(sched_blocked);
  LLIST_NULLIFY(sched_dead);

  return EXIT_SUCCESS;
}


/*========================================================================
 * Ajout a une file
 *========================================================================*/


PUBLIC u8_t sched_enqueue(u8_t queue, struct thread* th)
{
  /* Controle */
  if ( th == NULL)
    {
      return EXIT_FAILURE;
    }

  switch(queue)
    {
    
    case SCHED_RUNNING_QUEUE:
      
       LLIST_ADD(sched_running, th);
      th->state = THREAD_RUNNING;
      break;

    case SCHED_READY_QUEUE:
       /* Enfile selon le niveau de priorite */
      if (th->sched.static_prio < SCHED_PRIO_RR)
	{
	  sched_enqueue_staircase(th);
	}
      else
	{
	  sched_enqueue_roundrobin(th);
	}
            klib_bochs_print(" Putting ");
            klib_bochs_print(th->name);
            klib_bochs_print(" in ");
	    klib_bochs_print(" READY QUEUE \n");
      th->state = THREAD_READY;
      break;

    case SCHED_BLOCKED_QUEUE:
      LLIST_ADD(sched_blocked, th);
      th->state = THREAD_BLOCKED;
      break;
      
    case SCHED_DEAD_QUEUE:
      LLIST_ADD(sched_dead, th);
      th->state = THREAD_DEAD;
      break;

    default:
       return EXIT_FAILURE;
      
    }

    return EXIT_SUCCESS;

}


/*========================================================================
 * Retrait d une file
 *========================================================================*/


PUBLIC u8_t sched_dequeue(u8_t queue, struct thread* th)
{
  /* Controle */
  if ( th == NULL)
    {
      return EXIT_FAILURE;
    }

  switch(queue)
    {
    
    case SCHED_RUNNING_QUEUE:
       LLIST_REMOVE(sched_running, th);
      break;

    case SCHED_READY_QUEUE:
       LLIST_REMOVE(sched_ready[th->sched.dynamic_prio], th);
      break;

    case SCHED_BLOCKED_QUEUE:
       LLIST_REMOVE(sched_blocked, th);
      break;

    case SCHED_DEAD_QUEUE:
       LLIST_REMOVE(sched_dead, th);
      break;

    default:
       return EXIT_FAILURE;
      
    }

  return EXIT_SUCCESS;

}


/*========================================================================
 * Ordonnancement 
 *========================================================================*/


PUBLIC void sched_schedule(u8_t flag)
{

  struct thread* cur_th;
  struct thread* new_th;
  u8_t high_prio;

  /* Controle */
  if ( LLIST_ISNULL(sched_running) )
    {
      return;
    }

  /* Le thread courant */
  cur_th = sched_get_running_thread();
  if (cur_th == NULL)
    {
      return;
    }
 
  /* Decremente son quantum dynamique si appel par le PIT */
  if (flag == SCHED_FROM_PIT)
    {
      klib_bochs_print(" PIT ! (%d) \n", cur_th->sched.dynamic_quantum);
      cur_th->sched.dynamic_quantum--;
    }
  

  /* Trouve la file de plus haute priorite */
  high_prio = sched_get_higher_prio_queue();

  /* Avec cette condition, une tache de plus haute priorite est qd meme stoppee en fin de quantum  */
  if ( (cur_th->sched.dynamic_quantum<0) || (high_prio > cur_th->sched.dynamic_prio) || (cur_th->next_state != THREAD_READY) )
    {
      /* Enleve le thread courant de la file d execution */
      sched_dequeue(SCHED_RUNNING_QUEUE,cur_th);

      /* Ajoute le thread ou il faut en fonction du futur etat */
      switch(cur_th->next_state)
	{
	case THREAD_READY:
	  sched_enqueue(SCHED_READY_QUEUE,cur_th);
	  break;

	case THREAD_BLOCKED:
	  sched_enqueue(SCHED_BLOCKED_QUEUE,cur_th);
	  break;

	case THREAD_BLOCKED_SENDING:
	  {
	    /* Ajoute la thread a la liste d attente du receveur */
            /* Ajoute la thread a la liste d attente du receveur */
            klib_bochs_print(" Putting ");
            klib_bochs_print(cur_th->name);
            klib_bochs_print(" in ");
            klib_bochs_print((cur_th->ipc.send_to)->name);
            klib_bochs_print(" waiting list \n");


	    LLIST_ADD((cur_th->ipc.send_to)->ipc.receive_waitlist, cur_th);
	    cur_th->state = THREAD_BLOCKED_SENDING;
	    break;
	  }

	default:
	  sched_enqueue(SCHED_DEAD_QUEUE,cur_th);
	  break;
	}

      
      /* Choisis un nouveau thread */
      new_th = LLIST_GETHEAD(sched_ready[high_prio]);
      if (new_th == NULL)
	{
	  return;
	}
         
      /* Change le nouveau thread de queue */
      sched_dequeue(SCHED_READY_QUEUE,new_th);
      sched_enqueue(SCHED_RUNNING_QUEUE,new_th);
 

      klib_bochs_print(" ELECTING: ");
      klib_bochs_print(new_th->name);
      klib_bochs_print(" (prio: %d)\n", new_th->sched.dynamic_prio);

      /* Switch vers le nouveau thread */
      thread_switch_to(new_th);

    }

  return;
}


/*========================================================================
 * Thread courant
 *========================================================================*/


PUBLIC struct thread* sched_get_running_thread(void)
{
  return LLIST_GETHEAD(sched_running);
}


/*========================================================================
 * Enfile dans une file de la ready queue en RoundRobin
 *========================================================================*/


PRIVATE void sched_enqueue_roundrobin(struct thread* th)
{
  if (th->sched.dynamic_quantum < 0)
    {
      /* Reajuste son quantum */
      th->sched.dynamic_quantum = th->sched.static_quantum;
    }

  /* Enfile en fin de queue */
  LLIST_ADD(sched_ready[th->sched.dynamic_prio], th);

  return;
}



/*========================================================================
 * Enfile dans une file de la ready queue en Staircase
 *========================================================================*/


PRIVATE void sched_enqueue_staircase(struct thread* th)
{
  if (th->sched.dynamic_quantum < 0)
    {
      /* Decremente la priorite dynamique */
      if (th->sched.dynamic_prio > 0)
	{
	  klib_bochs_print("PWET %d", th->sched.dynamic_prio);
	  klib_bochs_print(th->name);
	  th->sched.dynamic_prio--;
	  /* Reaffecte un quantum */
	  th->sched.dynamic_quantum=th->sched.static_quantum;
	}
      else
	{
	  /* Ici, le thread est en fin de plus basse priorite, on decremente la priorite de tete */
	  th->sched.head_prio--;

	  if (th->sched.head_prio < 0)
	    {
	      /* Remet la priorite de tete a l originale en cas de fin de cycle */
	      th->sched.head_prio = th->sched.static_prio;
	      klib_bochs_print("PROUT ");
	      klib_bochs_print(th->name);
	    }

	  /* Ajuste le priorite dynamique sur la priorite de tete */
	  th->sched.dynamic_prio = th->sched.head_prio;
	  /* Ajuste le quantum en fonction de la priorite */
	  th->sched.dynamic_quantum = (th->sched.static_prio - th->sched.head_prio)*th->sched.static_quantum;

	}

    }

  /* Enfile en fin de queue */
  LLIST_ADD(sched_ready[th->sched.dynamic_prio], th);  

  return;
}




/*========================================================================
 * Trouve la file de la ready queue de plus haute priorite
 *========================================================================*/


PRIVATE u8_t sched_get_higher_prio_queue(void)
{
  u8_t i;

  /* Parcourt simplement les files de la ready queue */  
  for(i=SCHED_PRIO_MAX;(i!=0)&&(LLIST_ISNULL(sched_ready[i]));i--)
    {}

  return i;
}
